RIME 输入法折腾记录

2023/12/18


缘由

之所以开始折腾 RIME 输入法,是因为 Windows 11 自带的输入法不知从哪次的 Windows 更新开始,在 Windows 拼音输入法下输入时按下 Tab 键,就会出现如下提示:

Windows 输入法

开启这一选项意味着你输入的内容都会被发送到微软的服务器,这显然是对个人隐私保护不利的。

我当然可以选择不启用这一功能,但是每每想到这个输入法上存在着这么一个用不着的功能,还被绑定到 Tab 这个常用的按键,我就浑身难受。

小声提一下,你可以在 设置 > 时间&语言>语言 &区域中 找到设置,> Microsoft 拼音 > 词典和自学 > 尝试来自必应的文本建议 启用和关闭这一功能。

替代品

于是我开始搜寻自带输入法的替代品。很快,我找到来 RIME(中州韵) 输入法,这一输入法可以高度自定义,有足够的社区支持(可以抄现成的作业),支持 Windows、Linux、MacOs 三大操作系统,最重要的是,它完全开源!

你可以很容易地从官网下载它,Windows 版本的 RIME 输入法被叫作“小狼毫”。

参考

具体的配置过程主要参照这两篇博客:

以及其文中附带的 GitHub 上已经配置好的 repo。


主要功能

在本文成文时,我使用的是当前的最新 Windows 版本 0.15.0

在我的完全配置后,现在已经可以实现如下功能:

接下来,我会从入门开始逐一介绍如何配置上述这些功能。

折腾配置

配置文件解析

在 Windows 上,你可以通过任务栏的托盘(如下图)快速打开配置文件夹。

小声提一嘴,如果你和我一样只习惯拼音输入法,在安装中选择输入法的界面可以只选择朙月拼音选项,也能勉强算是开箱即用。

RIME 托盘图标
RIME 托盘右键菜单

主要的配置文件及其功能:

其它的也不太用得着,就先不介绍了。

外观

俗话说,美是第一生产力。RIME 输入法的默认外观不一定能符合你的需求,自己配置一份外观即能让自己用得更舒服,也能更符合自己的审美。先贴上我配置完后的外观:

RIME 外观

我们先打开上文中提到的 weasel.custom.yaml

可以看到,文件开头的部分:

customization:
  distribution_code_name: Weasel
  distribution_version: 0.15.0.0
  generator: "Weasel::UIStyleSettings"
  modified_time: "Sun Dec 17 22:14:58 2023"
  rime_version: 1.8.5

这些都不需要管,关键在于后面 patch 之后的部分。

你可以通过类似下面这样的配置项来告诉输入法,你希望在哪些应用中默认使用英文输入法。注意⚠️请使用合适的缩进并且不要使用 Tab 来作为缩进,这可能会导致输入法读取配置文件时出现错误;另外,这里的程序名可以通过任务管理器获取,并且必须全部小写,不得有大写字符。

app_options:
  windowsterminal.exe:
    ascii_mode: true
  vscode.exe:
    ascii_mode: true
  vscodium.exe:
    ascii_mode: true

在修改配置文件后,你可以通过托盘右键菜单中的“重新部署”来刷新输入法,应用新的配置。


在添加完对于特定应用的配置后,接下来才是重头戏:样式管理。这是我目前使用的样式并附上完整的注释:

style:
  color_scheme: BlueGrey               # 当前使用的颜色配置方案
  display_tray_icon: false             # 是否显示右下角图标
  horizontal: true                     # 横排显示
  font_face: "Microsoft YaHei"         # 候选字体
  font_point: 15                       # 候选字体大小
  label_font_face: "JetBrains Mono"    # 序号字体
  label_font_point: 14                 # 序号字体大小
  comment_font_face: "Microsoft YaHei" # 注释字体
  comment_font_point: 12               # 注释字体大小
  inline_preedit: true                 # 嵌入式候选窗单行显示
  layout:
    border: 6                # 边框宽度
    border_width: 0          # 边框宽度
    margin_x: 18             # 候选项左右边距
    margin_y: 18             # 候选项上下边距
    spacing: 12              # 候选项间距
    candidate_spacing: 24    # 候选项内部间距
    round_corner: 12         # 输入法候选框的圆角幅度,0为直角
    hilite_padding: 8        # 激活候选项背景色高度
    hilite_spacing: 3        # 序号和候选项之间的间隔
    hilited_corner_radius: 8 # 选词的圆角幅度,0为直角

然后就是配色方案的编写,不多说,直接上代码:

preset_color_schemes:
  BlueGrey:
    name: BlueGrey                         # 配置方案名称
    back_color: 0xF1EFEC                   # 候选框背景色
    border_color: 0xAEA490                 # 候选框边框颜色
    text_color: 0x333333                   # 已选择字文字颜色
    hilited_text_color: 0x000000           # 已选择字右侧拼音文字颜色
    hilited_back_color: 0x000000           # 已选择字右侧拼音背景色
    hilited_candidate_text_color: 0xFFFFFF # 已选择字颜色
    hilited_candidate_back_color: 0x645A45 # 已选择字背景色
    hilited_comment_text_color: 0xE0E0E0   # 已选择字右侧注释文字颜色
    hilited_label_color: 0xE0E0E0          # 已选择字左侧数字序号颜色
    candidate_text_color: 0x333333         # 未候选字颜色
    comment_text_color: 0x666666

再附加一张来自其它博主的包浆图例:

RIME 外观

小声说一句🤫:这里的颜色代码不是一般的 RGB 顺序,而是 BGR 顺序,因此你不能直接复制网上配色方案中的颜色代码。这里是一位大佬做的在线工具,你可以以图形化的方式编写配色方案。

再附上完整的 weasel.custom.yaml 文件:

customization:
  distribution_code_name: Weasel
  distribution_version: 0.15.0.0
  generator: "Weasel::UIStyleSettings"
  modified_time: "Sun Dec 17 22:14:58 2023"
  rime_version: 1.8.5
patch:
  app_options:
    windowsterminal.exe:
      ascii_mode: true
    vscode.exe:
      ascii_mode: true
    vscodium.exe:
      ascii_mode: true

  style:
    color_scheme: BlueGrey
    display_tray_icon: false             # 是否显示右下角图标
    horizontal: true                     # 横排显示
    font_face: "Microsoft YaHei"         # 候选字体
    font_point: 15                       # 候选字体大小
    label_font_face: "JetBrains Mono"    # 序号字体
    label_font_point: 14                 # 序号字体大小
    comment_font_face: "Microsoft YaHei" # 注释字体
    comment_font_point: 12               # 注释字体大小
    inline_preedit: true                 # 嵌入式候选窗单行显示
    layout:
      border: 6
      border_width: 0          # 边框宽度
      margin_x: 18             # 候选项左右边距
      margin_y: 18             # 候选项上下边距
      spacing: 12              # 候选项间距
      candidate_spacing: 24    # 候选项内部间距
      round_corner: 12         # 输入法候选框的圆角幅度,0为直角
      hilite_padding: 8        # 激活候选项背景色高度
      hilite_spacing: 3        # 序号和候选项之间的间隔
      hilited_corner_radius: 8 # 选词的圆角幅度,0为直角

  preset_color_schemes:
    BlueGrey:
      name: BlueGrey
      back_color: 0xF1EFEC                   # 候选框背景色
      border_color: 0xAEA490                 # 候选框边框颜色
      text_color: 0x333333                   # 已选择字文字颜色
      hilited_text_color: 0x000000           # 已选择字右侧拼音文字颜色
      hilited_back_color: 0x000000           # 已选择字右侧拼音背景色
      hilited_candidate_text_color: 0xFFFFFF # 已选择字颜色
      hilited_candidate_back_color: 0x645A45 # 已选择字背景色
      hilited_comment_text_color: 0xE0E0E0   # 已选择字右侧注释文字颜色
      hilited_label_color: 0xE0E0E0          # 已选择字左侧数字序号颜色
      candidate_text_color: 0x333333         # 未候选字颜色
      comment_text_color: 0x666666

创建自己的输入方案

这一步实际上非常简单。你只需要在配置文件夹下创建 [方案名称].schema.yaml 的文件,再参考官方的朙月拼音配置文件的写法,加以修改。目前,你只需要 Copy 朙月拼音配置文件,把其中的 schema_id, author, description, nameversion 改成自己的就 OK。

模糊音识别及拼音纠错

这一部分非常简单,你只需要在输入方案文件的最后加上如下代码:

speller:
  algebra:
    - erase/^xx$/
    - derive/^([zcs])h/$1/ # zh, ch, sh => z, c, s
    - derive/^([zcs])([^h])/$1h$2/ # z, c, s => zh, ch, sh
    - derive/([aei])n$/$1ng/ # en => eng, in => ing
    - derive/([aei])ng$/$1n/ # eng => en, ing => in
    - derive/([iu])an$/$lang/ # ian => iang, uan => uang
    - derive/([iu])ang$/$lan/ # iang => ian, uang => uan
    - derive/([aeiou])ng$/$1gn/        # dagn => dang
    - derive/([dtngkhrzcs])o(u|ng)$/$1o/  # zho => zhong|zhou
    - derive/ong$/on/                  # zhonguo => zhong guo
    - derive/ao$/oa/                   # hoa => hao
    - derive/([iu])a(o|ng?)$/a$1$2/    # tain => tian
    - abbrev/^([a-z]).+$/$1/  # 简拼(首字母)
    - abbrev/^([zcs]h).+$/$1/ # 简拼(zh, ch, sh)

其中每一行的用处也都写明,你可以根据自己的需求选择是否使用全部规则。


日本語输入

这是我个人的特殊需求,不需要的话可以跳过。对于其它语言输入法的设置也同理。

打开这个 GitHub 储存库,下载源代码,将其中以 yaml 为拓展名的文件直接复制到配置文件夹中,再修改 default.custom.yaml 中的 schema_list 字段为:

patch:
  schema_list:
    - {schema: pinyin}
    - {schema: japanese}

另外,你如果觉得配置文件夹中的文件太多太杂乱的话,你可以创建 dicts 文件夹(这也是我们之后中文词库存放文件夹),然后将 japanese.jmdict.dict.yaml, japanese.kana.dict.yaml, japanese.mozc.dict.yaml 都移动到其中,在修改 japanese.dict.yaml 文件为如下所示:

# nihong-hybrid.dict.yaml
# encoding: utf-8

---
name: japanese
version: 'v0.2-20180411'

import_tables:
  - dicts/japanese.mozc
  - dicts/japanese.jmdict
  - dicts/japanese.kana

重新部署程序,点击 F4 或者 Ctrl + 反引号,打开输入方案切换框,不出意外的话,框中应该会出现“日本語”的字样。选择它,你就可以使用ローマ字(罗马音)来进行日文输入了。


字符映射

这一步利用来输入法自带的 opencc 功能,得以通过字符映射的方式实现 emoji 等输入。

我们先在我们的语言配置文件(*.schema.yaml)中找到 switches 字段,在其中加入如下代码:

- name: mapping_suggestion
  reset: 1              # 此字段可以让输入法记忆开关状态 
  states: [ 关闭, 映射 ] # 此字段会在输入方案切换框中展示,可以调整需要展示的文字

添加完后应该会像这样:

switches:
  - name: ascii_mode
    reset: 0
    states: [ 中文, 西文 ]
  - name: mapping_suggestion
    reset: 1
    states: [ 关闭, 映射 ]
  - name: full_shape
    states: [ 半角, 全角 ]
  - name: simplification
    states: [ 漢字, 汉字 ]
  - name: ascii_punct
    states: [ 。,, ., ]

再在其后添加 mapping_suggestion 的定义:

mapping_suggestion:
  opencc_config: mapping.json # 存储映射词库定义的文件
  option_name: mapping_suggestion
  tips: all
  inherit_comment: false

再在 engine - filters 下加入如下字段:

- simplifier@emoji_suggestion

添加完后如下所示:

engine:
  filters:
    - simplifier@emoji_suggestion
    - simplifier@zh_simp # 简体过滤,此行可能可以省略
    - simplifier # 简体
    - uniquifier # rime基础驱动

最后,你可以到此下载需要的映射表,复制到配置文件目录下的 opencc 目录(没有可以自己新建),在将其中的 emoji.json 更名为 mapping.json

刷新程序,试试效果:

输入一月效果
输入笑效果

lua 脚本配置

顺便提一下,我不确定 lua 脚本的使用是否需要安装 lua 解释器,姑且贴一下:https:github.com/rjpcomputing/luaforwindows,如果你需要在 Windows 电脑上安装 lua,可能能用得上

先在配置文件目录下新建 lua 文件夹和 rime.lua 文件,再在 lua 文件夹下添加如下脚本:

-- time.lua
local function translator(input, seg)
    if (input == "time" or input == "when") then
        yield(Candidate("time", seg.start, seg._end, os.date("%H:%M"), " "))
        yield(Candidate("time", seg.start, seg._end, os.date("%H点%M分"), " "))
        yield(Candidate("time", seg.start, seg._end, os.date("%H:%M:%S"), " "))
        yield(Candidate("time", seg.start, seg._end, os.date("%H点%M分%S秒"), " "))
    end
end
return translator
-- week.lua
function translator(input, seg)
    if (input == "week" or input == "xingqiji") then
        local day_w=os.date("%w")
        local day_w1=""
        local day_w2=""
        local day_w3=""

        if day_w == "0" then 
            day_w1="星期日"
            day_w2="Sunday"
            day_w3="Sun."
        end
        if day_w == "1" then
            day_w1="星期一" 
            day_w2="Monday" 
            day_w3="Mon." 
        end
        if day_w == "2" then
            day_w1="星期二"
            day_w2="Tuesday"
            day_w3="Tues."
        end
        if day_w == "3" then
            day_w1="星期三"
            day_w2="Wednesday"
            day_w3="Wed."
        end
        if day_w == "4" then
            day_w1="星期四"
            day_w2="Thursday"
            day_w3="Thur."
        end
        if day_w == "5" then
            day_w1="星期五"
            day_w2="Friday"
            day_w3="Fri."
        end
        if day_w == "6" then
            day_w1="星期六"
            day_w2="Saturday"
            day_w3="Sat."
        end
        yield(Candidate("date", seg.start, seg._end, day_w1, " "))
        yield(Candidate("date", seg.start, seg._end, day_w2, " "))
        yield(Candidate("date", seg.start, seg._end, day_w3, " "))
        yield(Candidate("week", seg.start, seg._end, os.date("%w"),""))
    end
end
return translator
-- date.lua
local function translator(input, seg)
    if (input == "date" or input == "riqi") then
        ------------------------------------------------------------------------------------
        --普通日期1,类似2022年01月02日
        date1=os.date("%Y年%m月%d日")
        date_y=os.date("%Y") --取年
        date_m=os.date("%m") --取月
        date_d=os.date("%d") --取日
        --yield(Candidate("date", seg.start, seg._end, date1, " "))
        ------------------------------------------------------------------------------------
        --普通日期2,类似2022年1月1日
        num_m=os.date("%m")+0
        num_m1=math.modf(num_m)
        num_d=os.date("%d")+0
        num_d1=math.modf(num_d)
        date2=os.date("%Y年")..tostring(num_m1).."月"..tostring(num_d1).."日"
        yield(Candidate("date", seg.start, seg._end, date2, " "))
        ------------------------------------------------------------------------------------
        --普通日期3,类似1月1日
        num_m=os.date("%m")+0
        num_m1=math.modf(num_m)
        num_d=os.date("%d")+0
        num_d1=math.modf(num_d)
        date3=tostring(num_m1).."月"..tostring(num_d1).."日"
        yield(Candidate("date", seg.start, seg._end, date3, " "))
        yield(Candidate("date", seg.start, seg._end, os.date("%Y/%m/%d"), " "))
        yield(Candidate("date", seg.start, seg._end, os.date("%Y-%m-%d"), " "))
        ------------------------------------------------------------------------------------
        --大写日期,类似二〇二〇年十一月二十六日
        date_y=date_y:gsub("%d",{
            ["1"]="一",
            ["2"]="二",
            ["3"]="三",
            ["4"]="四",
            ["5"]="五",
            ["6"]="六",
            ["7"]="七",
            ["8"]="八",
            ["9"]="九",
            ["0"]="〇",
        })
        date_y=date_y.."年"
        date_m=date_m:gsub("%d",{
            ["1"]="一",
            ["2"]="二",
            ["3"]="三",
            ["4"]="四",
            ["5"]="五",
            ["6"]="六",
            ["7"]="七",
            ["8"]="八",
            ["9"]="九",
            ["0"]="",
        })
        date_m=date_m.."月"
        if num_m1==10 then date_m="十月" end
        if num_m1==11 then date_m="十一月" end
        if num_m1==12 then date_m="十二月" end
        date_d=date_d:gsub("%d",{
            ["1"]="一",
            ["2"]="二",
            ["3"]="三",
            ["4"]="四",
            ["5"]="五",
            ["6"]="六",
            ["7"]="七",
            ["8"]="八",
            ["9"]="九",
            ["0"]="",
        })
        date_d=date_d.."日"
        if num_d1>9 then
            if num_d1<19 then
            date_d="十"..string.sub(date_d,4,#date_d)
            end
        end
        if num_d1>19 then
            date_d=string.sub(date_d,1,3).."十"..string.sub(date_d,4,#date_d)
        end
        date4=date_y..date_m..date_d
        yield(Candidate("date", seg.start, seg._end, date4, " "))
        ------------------------------------------------------------------------------------
        --英文日期
            local date_d=os.date("%d")
            local date_m=os.date("%m")
            local date_y=os.date("%Y")
            local date_m1=""
            local date_m2=""
            if date_m=="01" then 
                date_m1="Jan."
                date_m2="January"
            end
            if date_m=="02" then 
                date_m1="Feb."
                date_m2="February"
            end
            if date_m=="03" then 
                date_m1="Mar."
                date_m2="March"
            end
            if date_m=="04" then 
                date_m1="Apr."
                date_m2="April"
            end
            if date_m=="05" then 
                date_m1="May."
                date_m2="May"
            end
            if date_m=="06" then 
                date_m1="Jun."
                date_m2="June"
            end
            if date_m=="07" then 
                date_m1="Jul."
                date_m2="July"
            end
            if date_m=="08" then 
                date_m1="Aug."
                date_m2="August"
            end
            if date_m=="09" then 
                date_m1="Sept."
                date_m2="September"
            end
            if date_m=="10" then 
                date_m1="Oct."
                date_m2="October"
            end
            if date_m=="11" then 
                date_m1="Nov."
                date_m2="November"
            end
            if date_m=="12" then 
                date_m1="Dec."
                date_m2="December"
            end
        
            if date_d=="0" then 
                symbal="st" 
            elseif date_d=="1" then
                symbal="nd" 
            elseif date_d=="2" then 
                symbal="rd" 
            else
                symbal="th"
            end
        date5=date_m1.." "..date_d..symbal..", "..date_y
        date6=date_m2.." "..date_d..symbal..", "..date_y
        
        yield(Candidate("date", seg.start, seg._end, date5, " "))
        yield(Candidate("date", seg.start, seg._end, date6, " "))
    end
end
return translator

再在 rime.lua 文件中添加如下定义:

time_translator = require("time")
week_translator = require("week")
date_translator = require("date")

最后,在输入方案配置文件中的 engine - translators 下添加:

- lua_translator@time_translator
- lua_translator@week_translator
- lua_translator@date_translator

添加完后应该会像这样:

engine:
  processors:
    - ascii_composer
    - recognizer
    - key_binder
    - speller
    - punctuator
    - selector
    - navigator
    - express_editor
  segmentors:
    - ascii_segmentor
    - matcher
    - abc_segmentor
    - punct_segmentor
    - fallback_segmentor
  translators:
    - lua_translator@time_translator
    - lua_translator@week_translator
    - lua_translator@date_translator
    - punct_translator
    - "table_translator@custom_phrase"
    - reverse_lookup_translator
    - script_translator
  filters:
    - simplifier@mapping_suggestion
    - simplifier
    - uniquifier

刷新程序,在中文模式下输入“date”、“riqi”等设定词,就可以看到 lua 脚本中定义好的输出了:

输入“time”
输入“week”

导入词库

首先,在 default.custom.yaml 中加入:

translator:
  # 允许使用用户词库
  enable_user_dict: true

在配置文件目录下创建 chinese_simp.dict.yaml,这个文件夹会用来存放指向所需的词库文件的路径。

修改输入方案配置文件下 translator - dictionary 为:chinese_simp,即指向我们刚刚创建的文件。

这里下载其中的 base.dict.yaml, ext.dict.yaml8105.dict.yaml 词库文件,放到前文里创建的 dicts 目录下。

修改 chinese_simp.dict.yaml 如下所示:

name: chinese_simp # 注意name和文件名一致
version: "0.0.1"
sort: by_weight
use_preset_vocabulary: true
# 此处为输入法所用到的词库,既补充拓展词库的地方
import_tables:
  - dicts/base
  - dicts/ext
  - dicts/8105

注意⚠️:前文的字符映射要在这里的导入词库配置完之后才能够使用映射表中的所有内容。


最后,放上我的完整的输入方案配置文件 pinyin.schema.yaml

schema:
  schema_id: pinyin
  name: 拼音
  version: '0.1'
  dependencies:
    - stroke

switches:
  - name: ascii_mode
    reset: 0
    states: [ 中文, 西文 ]
  - name: mapping_suggestion
    reset: 1
    states: [ 关闭, 映射 ]
  - name: full_shape
    states: [ 半角, 全角 ]
  - name: simplification
    states: [ 漢字, 汉字 ]
  - name: ascii_punct
    states: [ 。,, ., ]

engine:
  processors:
    - ascii_composer
    - recognizer
    - key_binder
    - speller
    - punctuator
    - selector
    - navigator
    - express_editor
  segmentors:
    - ascii_segmentor
    - matcher
    - abc_segmentor
    - punct_segmentor
    - fallback_segmentor
  translators:
    - lua_translator@time_translator
    - lua_translator@week_translator
    - lua_translator@date_translator
    - punct_translator
    - "table_translator@custom_phrase"
    - reverse_lookup_translator
    - script_translator
  filters:
    - simplifier@mapping_suggestion
    - simplifier
    - uniquifier

mapping_suggestion:
  opencc_config: mapping.json
  option_name: mapping_suggestion
  tips: all
  inherit_comment: false

speller:
  alphabet: zyxwvutsrqponmlkjihgfedcba
  delimiter: " '"
  algebra:
    - erase/^xx$/
    - derive/([aei])n$/$1ng/ # en => eng, in => ing
    - derive/([aei])ng$/$1n/ # eng => en, ing => in
    - derive/([iu])an$/$lang/ # ian => iang, uan => uang
    - derive/([iu])ang$/$lan/ # iang => ian, uang => uan
    - derive/([aeiou])ng$/$1gn/        # dagn => dang
    - derive/ong$/on/                  # zhonguo => zhong guo
    - derive/ao$/oa/                   # hoa => hao
    - derive/([iu])a(o|ng?)$/a$1$2/    # tain => tian
    - abbrev/^([a-z]).+$/$1/  # 简拼(首字母)
    - abbrev/^([zcs]h).+$/$1/ # 简拼(zh, ch, sh)

translator:
  dictionary: chinese_simp
  preedit_format:
    - xform/([nl])v/$1ü/
    - xform/([nl])ue/$1üe/
    - xform/([jqxy])v/$1u/

custom_phrase:
  dictionary: ""
  user_dict: custom_phrase
  db_class: stabledb
  enable_completion: false
  enable_sentence: false
  initial_quality: 1

reverse_lookup:
  dictionary: stroke
  enable_completion: true
  prefix: "`"
  suffix: "'"
  tips: 〔筆畫〕
  preedit_format:
    - xlit/hspnz/一丨丿丶乙/
  comment_format:
    - xform/([nl])v/$1ü/

punctuator:
  import_preset: symbols

key_binder:
  import_preset: default

recognizer:
  import_preset: default
  patterns:
    punct: '^/([0-9]0?|[A-Za-z]+)$'
    reverse_lookup: "`[a-z]*'?$"

感谢阅读!

点此查看原文